package gdou.gym.sys.order;

import gdou.gym.dao.SpaceTimeMapper;
import gdou.gym.entity.SpaceTime;
import gdou.gym.exception.StateCode;
import gdou.gym.exception.SysException;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Component
@Slf4j
public class RemainImplRedis implements Remain {
    @Resource
    private SpaceTimeMapper spaceTimeMapper;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private SqlSessionFactory sqlSessionFactory;
    private static final String KEY = "remain";
    private volatile static boolean update = false;
    private static final Object VALUE = new Object();
    private HashOperations<String, Integer, Integer> ops;
    private static final ConcurrentHashMap<Integer, Integer> temp = new ConcurrentHashMap<>();
    public static final ConcurrentHashMap<Integer, Object> remove = new ConcurrentHashMap<>();

    @Override
    public void request(Integer id) {
        Integer remain = getRemain(id);
        if (remain == null || remain <= 0) throw new SysException(StateCode.ORDER_ERROR);
        temp.put(id, remain);
        put(id, 0);
    }

    @Override
    public void recover(Integer id) {
        put(id, temp.remove(id));
    }

    @Override
    public Map<Integer, Integer> getRemainMap() {
        return ops.entries(KEY);
    }

    @Override
    @PostConstruct
    public void initRemain() {
        List<SpaceTime> times = spaceTimeMapper.initRemain();
        ops = redisTemplate.opsForHash();
        HashMap<Integer, Integer> map = new HashMap<>();
        for (SpaceTime time : times) {
            map.put(time.getId(), time.getRemain());
        }
        ops.putAll(KEY, map);
    }

    @Override
    public void clear(Integer id) {
        ops.put(KEY, id, 0);
    }

    @Override
    public boolean put(Integer id, Integer count) {
        ops.put(KEY, id, count);
        update = true;
        return true;
    }


    @Override
    public boolean increase(Integer id, int count) {
        Integer cnt = ops.get(KEY, id);
        if (cnt == null) return false;
        ops.put(KEY, id, cnt + count);
        update = true;
        return true;
    }

    @Override
    public boolean remove(Integer id) {
        if (ops.delete(KEY, id) == 1 && (update = true)) {
            remove.put(id, VALUE);
            return true;
        }
        return false;

    }

    @Override
    public boolean putOrIncrease(Integer id, Integer count) {
        Integer cnt = ops.get(KEY, id);
        ops.put(KEY, id, cnt == null ? count : cnt + count);
        update = true;
        return true;
    }

    @Override
    public boolean removeOrDecrease(Integer id, Integer count) {
        Integer cnt = ops.get(KEY, id);
        if (cnt == null) return true;
        if (cnt > count) ops.put(KEY, id, count - cnt);
        else {
            ops.delete(KEY, id);
            remove.put(id, VALUE);
        }
        update = true;
        return true;
    }

    @Override
    public boolean decrease(Integer id, int count) {
        Integer cnt = ops.get(KEY, id);
        if (cnt == null || cnt < count) return false;
        ops.put(KEY, id, cnt - count);
        update = true;
        return true;
    }

    public List<Integer> multiGet(Collection<Integer> hashKeys) {
        return ops.multiGet(KEY, hashKeys);
    }

    @Override
    public Integer getRemain(Integer id) {
        return ops.get(KEY, id);
    }

    @Override
    public void batchUpdate() {
        SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
        SpaceTimeMapper mapper = session.getMapper(SpaceTimeMapper.class);
        for (Map.Entry<Integer, Integer> entry : ops.entries(KEY).entrySet()) {
            mapper.updateSpaceTimeRemain(entry.getKey(), entry.getValue());
        }
        remove.forEachKey(5, mapper::deleteByPrimaryKey);
        remove.clear();
        session.commit();
        session.close();
    }

    @PreDestroy
    public void PreDestroy() {
        log.info("destroy...........");
        update();
    }

    @Scheduled(cron = "0/10 * *  * * ? ")
    public void update() {
        if (update) {
            log.info("update............");
            batchUpdate();
            update = false;
        }
    }

}
